package prefix.integration.wm;

import java.math.BigDecimal;
import java.util.Date;

import javax.persistence.LockModeType;

import models.WmTransaction;
import models.WmTransaction.Status;
import models.WmTransaction.Type;
import models.WmWallet;
import prefix.integration.wm.WMException.CODE;
import prefix.util.JPAUtil;
import prefix.util.OverallUtil;

public class WMProcessor {

    public WMProcessor() {
    }

    public Integer createWallet(Integer walletId, String name, WmWallet.Type type) throws WMException {
        try {
            JPAUtil.beginTransaction();
            WmWallet wallet = new WmWallet(WmWallet.Status.ACTIVE, type);
            wallet.setId(walletId);
            wallet.setName(name);
            JPAUtil.em().persist(wallet);
            JPAUtil.commitTransaction();
            return wallet.getId();
        } catch (Exception e) {
            throw new WMException(CODE.UNKNOWN, e);
        } finally {
            JPAUtil.closeEM();
        }
    }

    public void buyNotes(int walletId, BigDecimal amount) throws WMException {

        try {
            WmWallet wallet = JPAUtil.em().find(WmWallet.class, walletId, LockModeType.PESSIMISTIC_WRITE);
            if (OverallUtil.round2Decimal(wallet.getBalance().subtract(amount)).doubleValue() < 0)
                throw new WMException(CODE.NOT_ENOUGH_MONEY);
            wallet.setBalancePendingForInvest(OverallUtil
                    .round2Decimal(wallet.getBalancePendingForInvest().add(amount)));
            wallet.setBalance(OverallUtil.round2Decimal(wallet.getBalance().subtract(amount)));
            wallet.setUpdateD(new Date());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new WMException(CODE.UNKNOWN, e);
        }
    }

    public void releasePendingInvestAmount(int walletId, BigDecimal amount) throws WMException {
        try {

            WmWallet wallet = JPAUtil.em().find(WmWallet.class, walletId, LockModeType.PESSIMISTIC_WRITE);
            if (OverallUtil.round2Decimal(wallet.getBalancePendingForInvest().subtract(amount)).doubleValue() < 0)
                throw new WMException(CODE.NOT_ENOUGH_PENDING_INVEST_TO_RELEASE);
            wallet.setBalancePendingForInvest(OverallUtil.round2Decimal(wallet.getBalancePendingForInvest().subtract(
                    amount)));
            wallet.setBalance(OverallUtil.round2Decimal(wallet.getBalance().add(amount)));
            wallet.setUpdateD(new Date());

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new WMException(CODE.UNKNOWN, e);
        }

    }

    public WmTransaction executeLoadFunds(Integer fromWalletId, Integer toWalletId, BigDecimal amount)
            throws WMException {

        WmTransaction trans = new WmTransaction(fromWalletId, toWalletId, amount, Type.LOAD);
        WmWallet fromWallet = JPAUtil.em()
                .find(WmWallet.class, trans.getFromWalletId(), LockModeType.PESSIMISTIC_WRITE);
        WmWallet toWallet = JPAUtil.em().find(WmWallet.class, trans.getToWalletId(), LockModeType.PESSIMISTIC_WRITE);
        executeLoadFundsWalletSetting(fromWallet, toWallet, trans);
        JPAUtil.em().persist(trans);
        return trans;
    }

    public int createWithdrawl(Integer fromWalletId, Integer toWalletId, BigDecimal amount) throws WMException {
        parameterValidate(fromWalletId, toWalletId, amount);
        return internalTransfer(fromWalletId, toWalletId, amount, WmTransaction.Type.WITHDRAW);
    }

    public WmTransaction rollbackWithdrawl(Integer transId) {

        WmTransaction trans = JPAUtil.em().find(WmTransaction.class, transId, LockModeType.PESSIMISTIC_WRITE);
        WmWallet fromWallet = JPAUtil.em()
                .find(WmWallet.class, trans.getFromWalletId(), LockModeType.PESSIMISTIC_WRITE);
        WmWallet toWallet = JPAUtil.em().find(WmWallet.class, trans.getToWalletId(), LockModeType.PESSIMISTIC_WRITE);

        rollbackWithdrawlWalletSetting(fromWallet, toWallet, trans);
        return trans;
    }

    public WmTransaction executeWithdrawl(Integer transId) {

        WmTransaction trans = JPAUtil.em().find(WmTransaction.class, transId, LockModeType.PESSIMISTIC_WRITE);
        WmWallet fromWallet = JPAUtil.em()
                .find(WmWallet.class, trans.getFromWalletId(), LockModeType.PESSIMISTIC_WRITE);
        WmWallet toWallet = JPAUtil.em().find(WmWallet.class, trans.getToWalletId(), LockModeType.PESSIMISTIC_WRITE);
        executeWithdrawlWalletSetting(fromWallet, toWallet, trans);
        return trans;
    }

    private void executeWithdrawlWalletSetting(WmWallet fromWallet, WmWallet toWallet, WmTransaction trans) {

        fromWallet.setPendingWithdraw(OverallUtil.round2Decimal(fromWallet.getPendingWithdraw().subtract(
                trans.getAmount())));
        toWallet.setPendingLoads(OverallUtil.round2Decimal(toWallet.getPendingLoads().subtract(trans.getAmount())));
        toWallet.setBalance(OverallUtil.round2Decimal(toWallet.getBalance().add(trans.getAmount())));
        fromWallet.setUpdateD(new Date());
        toWallet.setUpdateD(new Date());
        trans.setValues(WmTransaction.Status.EXECUTED, fromWallet.getBalance(), toWallet.getBalance());
    }

    private void executeLoadFundsWalletSetting(WmWallet fromWallet, WmWallet toWallet, WmTransaction trans) {
        fromWallet.setBalance(OverallUtil.round2Decimal(fromWallet.getBalance().subtract(trans.getAmount())));
        toWallet.setBalance(OverallUtil.round2Decimal(toWallet.getBalance().add(trans.getAmount())));
        fromWallet.setUpdateD(new Date());
        toWallet.setUpdateD(new Date());
        trans.setValues(WmTransaction.Status.EXECUTED, fromWallet.getBalance(), toWallet.getBalance());
    }

    private void rollbackWithdrawlWalletSetting(WmWallet fromWallet, WmWallet toWallet, WmTransaction trans) {
        fromWallet.setPendingLoads(OverallUtil.round2Decimal(fromWallet.getPendingWithdraw()
                .subtract(trans.getAmount())));
        fromWallet.setBalance(OverallUtil.round2Decimal(fromWallet.getBalance().add(trans.getAmount())));
        toWallet.setPendingLoads(OverallUtil.round2Decimal(toWallet.getPendingLoads().subtract(trans.getAmount())));
        fromWallet.setUpdateD(new Date());
        toWallet.setUpdateD(new Date());
        trans.setValues(WmTransaction.Status.ROLLBACK, fromWallet.getBalance(), toWallet.getBalance());
    }

    private void withdrawlCreateWalletSetting(WmWallet fromWallet, WmWallet toWallet, BigDecimal amount) {
        toWallet.setPendingLoads(OverallUtil.round2Decimal(toWallet.getPendingLoads().add(amount)));
        toWallet.setUpdateD(new Date());
        fromWallet.setPendingWithdraw(OverallUtil.round2Decimal(fromWallet.getPendingWithdraw().add(amount)));
        fromWallet.setBalance(OverallUtil.round2Decimal(fromWallet.getBalance().subtract(amount)));
        fromWallet.setUpdateD(new Date());
    }

    private Integer internalTransfer(int fromWalletId, int toWalletId, BigDecimal amount, WmTransaction.Type type)
            throws WMException {
        try {
            WmWallet toWallet = JPAUtil.em().find(WmWallet.class, toWalletId, LockModeType.PESSIMISTIC_WRITE);
            WmWallet fromWallet = JPAUtil.em().find(WmWallet.class, fromWalletId, LockModeType.PESSIMISTIC_WRITE);
            walletValidate(fromWallet, toWallet, amount);
            typeValidate(type, fromWallet, toWallet);

            WmTransaction trans = new WmTransaction(fromWalletId, toWalletId, amount, type);
            JPAUtil.em().persist(trans);
            withdrawlCreateWalletSetting(fromWallet, toWallet, amount);
            return trans.getId();
        } catch (Exception e) {
            e.printStackTrace();
            throw new WMException(CODE.INTERNAL_TRANSFER_ERROR, e);
        }
    }

    private void typeValidate(WmTransaction.Type type, WmWallet fromWallet, WmWallet toWallet) throws WMException {
        if (fromWallet.equals(toWallet))
            throw new WMException(CODE.INVALID_WALLET);
        switch (type) {
        case LOAD:
            if (!WmWallet.Type.SYSTEM.equals(fromWallet.getType())
                    || !WmWallet.Type.CUSTOMER.equals(toWallet.getType()))
                throw new WMException(CODE.INVALID_WALLET_TYPE);
            break;
        case TRANSFER:
            if (!WmWallet.Type.CUSTOMER.equals(fromWallet.getType())
                    || !WmWallet.Type.CUSTOMER.equals(toWallet.getType()))
                throw new WMException(CODE.INVALID_WALLET_TYPE);
            break;
        case WITHDRAW:
            if (!WmWallet.Type.CUSTOMER.equals(fromWallet.getType())
                    || !WmWallet.Type.SYSTEM.equals(toWallet.getType()))
                throw new WMException(CODE.INVALID_WALLET_TYPE);
            break;
        case FUNDS_TO_SYSTEM:
            if (!WmWallet.Type.CUSTOMER.equals(fromWallet.getType())
                    || !WmWallet.Type.SYSTEM.equals(toWallet.getType()))
                throw new WMException(CODE.INVALID_WALLET_TYPE);
            break;
        default:
            throw new WMException(CODE.INVALID_WALLET_TYPE);

        }
    }

    public WmTransaction executePendingInvestTransfer(Integer fromWalletId, Integer toWalletId, BigDecimal amount)
            throws WMException {

        WmTransaction trans = new WmTransaction(fromWalletId, toWalletId, amount, Type.TRANSFER);

        WmWallet fromWallet = JPAUtil.em()
                .find(WmWallet.class, trans.getFromWalletId(), LockModeType.PESSIMISTIC_WRITE);
        WmWallet toWallet = JPAUtil.em().find(WmWallet.class, trans.getToWalletId(), LockModeType.PESSIMISTIC_WRITE);
        if (fromWallet == null || toWallet == null)
            throw new WMException(CODE.INVALIDE_WALLET);
        this.typeValidate(Type.TRANSFER, fromWallet, toWallet);
        if (OverallUtil.round2Decimal(fromWallet.getBalancePendingForInvest().subtract(trans.getAmount()))
                .doubleValue() < 0)
            throw new WMException(CODE.NO_MONEY_TRANSFER);
        executePendingInvestTransferWalletSetting(fromWallet, toWallet, trans);
        JPAUtil.em().persist(trans);
        return trans;
    }

    public WmTransaction executeTransfer(Integer fromWalletId, Integer toWalletId, BigDecimal amount)
            throws WMException {
        WmTransaction trans = new WmTransaction(fromWalletId, toWalletId, amount, Type.TRANSFER);

        WmWallet fromWallet = JPAUtil.em()
                .find(WmWallet.class, trans.getFromWalletId(), LockModeType.PESSIMISTIC_WRITE);
        WmWallet toWallet = JPAUtil.em().find(WmWallet.class, trans.getToWalletId(), LockModeType.PESSIMISTIC_WRITE);
        if (fromWallet == null || toWallet == null)
            throw new WMException(CODE.INVALIDE_WALLET);
        this.typeValidate(Type.TRANSFER, fromWallet, toWallet);
        if (OverallUtil.round2Decimal(fromWallet.getBalance().subtract(trans.getAmount())).doubleValue() < 0)
            throw new WMException(CODE.NO_MONEY_TRANSFER);
        executeTransferWalletSetting(fromWallet, toWallet, trans);
        JPAUtil.em().persist(trans);
        return trans;
    }

    public WmTransaction executeFundsToSystemWallet(Integer fromWalletId, Integer toWalletId, BigDecimal amount)
            throws WMException {
        WmTransaction trans = new WmTransaction(fromWalletId, toWalletId, amount, Type.LOAD);

        WmWallet fromWallet = JPAUtil.em()
                .find(WmWallet.class, trans.getFromWalletId(), LockModeType.PESSIMISTIC_WRITE);
        WmWallet toWallet = JPAUtil.em().find(WmWallet.class, trans.getToWalletId(), LockModeType.PESSIMISTIC_WRITE);
        if (fromWallet == null || toWallet == null)
            throw new WMException(CODE.INVALIDE_WALLET);
        this.typeValidate(Type.FUNDS_TO_SYSTEM, fromWallet, toWallet);
        if (OverallUtil.round2Decimal(fromWallet.getBalance().subtract(trans.getAmount())).doubleValue() < 0)
            throw new WMException(CODE.NO_MONEY_TRANSFER);

        fromWallet.setBalance(OverallUtil.round2Decimal(fromWallet.getBalance().subtract(amount)));
        toWallet.setBalance(OverallUtil.round2Decimal(toWallet.getBalance().add(amount)));
        fromWallet.setUpdateD(new Date());
        toWallet.setUpdateD(new Date());
        trans.setValues(WmTransaction.Status.EXECUTED, fromWallet.getBalance(), toWallet.getBalance());
        JPAUtil.em().persist(trans);
        return trans;
    }

    private void executePendingInvestTransferWalletSetting(WmWallet fromWallet, WmWallet toWallet, WmTransaction trans) {
        fromWallet.setBalancePendingForInvest(OverallUtil.round2Decimal(fromWallet.getBalancePendingForInvest()
                .subtract(trans.getAmount())));
        toWallet.setBalance(OverallUtil.round2Decimal(toWallet.getBalance().add(trans.getAmount())));
        fromWallet.setUpdateD(new Date());
        toWallet.setUpdateD(new Date());
        trans.setValues(WmTransaction.Status.EXECUTED, fromWallet.getBalance(), toWallet.getBalance());
    }

    private void executeTransferWalletSetting(WmWallet fromWallet, WmWallet toWallet, WmTransaction trans) {
        fromWallet.setBalance(OverallUtil.round2Decimal(fromWallet.getBalance().subtract(trans.getAmount())));
        toWallet.setBalance(OverallUtil.round2Decimal(toWallet.getBalance().add(trans.getAmount())));
        fromWallet.setUpdateD(new Date());
        toWallet.setUpdateD(new Date());
        trans.setValues(WmTransaction.Status.EXECUTED, fromWallet.getBalance(), toWallet.getBalance());
    }

    public BigDecimal getWalletBalance(final Integer walletId) {
        if (walletId == null || walletId.intValue() <= 0)
            return null;

        WmWallet wallet = JPAUtil.em().find(WmWallet.class, walletId);
        if (wallet == null)
            return null;
        return wallet.getBalance();
    }

    public BigDecimal getWalletPendingWithdrawBalance(final Integer walletId) {
        if (walletId == null || walletId.intValue() <= 0)
            return null;

        WmWallet wallet = JPAUtil.em().find(WmWallet.class, walletId);
        if (wallet == null)
            return null;
        return wallet.getPendingWithdraw();

    }

    public BigDecimal getWalletPendingLoadBalance(final Integer walletId) throws Throwable {
        if (walletId == null || walletId.intValue() <= 0)
            return null;
        WmWallet wallet = JPAUtil.em().find(WmWallet.class, walletId);
        if (wallet == null)
            return null;
        return wallet.getPendingLoads();
    }

    public BigDecimal getWalletPendingInvest(final Integer walletId) {
        if (walletId == null || walletId.intValue() <= 0)
            return null;
        WmWallet wallet = JPAUtil.em().find(WmWallet.class, walletId);
        if (wallet == null)
            return null;
        return wallet.getBalancePendingForInvest();

    }

    public BigDecimal getWalletLockedForInvest(final Integer walletId) throws Throwable {
        if (walletId == null || walletId.intValue() <= 0)
            return null;

        WmWallet wallet = JPAUtil.em().find(WmWallet.class, walletId);
        if (wallet == null)
            return null;
        return wallet.getBalanceLockedForInvest();

    }

    private void walletValidate(WmWallet fromWallet, WmWallet toWallet, BigDecimal amount) throws WMException {
        if (toWallet == null || fromWallet == null) {
            throw new WMException(CODE.INVALIDE_WALLET);
        }
        if ((fromWallet.getBalance().subtract(amount)).longValue() < 0) {
            throw new WMException(CODE.NOT_ENOUGH_MONEY);
        }
    }

    private void parameterValidate(int fromWalletId, int toWalletId, BigDecimal amount) throws WMException {
        if (fromWalletId <= 0 || toWalletId <= 0 || amount == null || amount.longValue() <= 0)
            throw new WMException(CODE.INVALIDE_PARAMETER);
        if (fromWalletId == toWalletId)
            throw new WMException(CODE.FROM_TO_WALLET_SAME);
    }

}
